﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;

namespace MyHome.Server.PluginBase
{
    /// <summary>
    /// A connection between two <see cref="IPLugin"/>s
    /// </summary>
    /// <typeparam name="T">The type of data to be forwarded by this connection</typeparam>
    public class PluginConnection<T> : IPluginConnection<T>
    {
        private IInputPlugin<T> input;
        private IOutputPlugin<T> output;

        /// <summary>
        /// Initializes a new instance of the <see cref="PluginConnection{T}"/> class.
        /// </summary>
        public PluginConnection()
        {
            Id = -1;
        }

        /// <summary>
        /// Gets or sets the unique identifier.
        /// </summary>
        /// <value>
        /// The unique identifier.
        /// </value>
        public int Id { get; set; }

        /// <summary>
        /// Gets or sets the input for this connection.
        /// </summary>
        /// <value>
        /// The input to connect to.
        /// </value>
        public IInputPlugin<T> Input
        {
            get
            {
                return input;
            }
            set
            {
                var old = input;
                input = value;
                if (value != null)
                    InputId = value.Id;
                else
                    InputId = -1;
                OnInputChanged(old, value);
            }
        }

        /// <summary>
        /// Gets or sets the output for this connection.
        /// </summary>
        /// <value>
        /// The output to connect to.
        /// </value>
        public IOutputPlugin<T> Output
        {
            get
            {
                return output;
            }
            set
            {
                output = value;
                if (value != null)
                    OutputId = value.Id;
                else
                    OutputId = -1;
            }
        }

        /// <summary>
        /// Gets or sets the input identifier.
        /// </summary>
        /// <value>
        /// The input identifier.
        /// </value>
        public int InputId { get; set; }

        /// <summary>
        /// Gets or sets the output identifier.
        /// </summary>
        /// <value>
        /// The output identifier.
        /// </value>
        public int OutputId { get; set; }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" }, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            var pluginConnection = obj as IPluginConnection<T>;
            if (pluginConnection == null)
                return false;

            return Id == pluginConnection.Id;
        }

        /// <summary>
        /// Determines whether the specified <see cref="IPluginConnection{T}" }, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="IPluginConnection{T}" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="IPluginConnection{T}" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public bool Equals(IPluginConnection<T> obj)
        {
            if (obj == null)
                return false;

            return Id == obj.Id;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return Id;
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return "PluginConnection";
        }

        private void Input_InputChanged(object sender, ValueChangedEventArgs<T> e)
        {
            var input = sender as IInputPlugin<T>;
            if (input == null || input.State != PluginState.Running)
                return;

            var output = Output;
            if (output == null || output.State != PluginState.Running)
                return;

            Trace.TraceInformation("PluginConnection \"{0}\", Id: {1} is sending value {2} from {3} \"{4}\", Id: {5} to {6} \"{7}\", Id: {8}", 
                this, Id, e.NewValue, input.PluginType.ToString(), input, input.Id, output.PluginType.ToString(), output, output.Id);
            output.SendOutputAsync(e.NewValue);
        }

        private void OnInputChanged(IInputPlugin<T> oldValue, IInputPlugin<T> newValue)
        {
            if (oldValue != null)
                oldValue.InputChanged -= Input_InputChanged;

            if (newValue != null)
                newValue.InputChanged += Input_InputChanged;
        }
    }
}